#include <tsin/library/TouhouPlayerFactory.hpp>

#include <tsin/library/PlayerEvent.hpp>
#include <tsin/library/GameViewport.hpp>
#include <tsin/library/Game.hpp>

#include <tsin/runtime/Player.hpp>
#include <tsin/runtime/GameRuntime.hpp>
#include <tsin/input/Input.hpp>
#include <tsin/input/InputEvent.hpp>
#include <tsin/display/Display.hpp>
#include <tsin/display/Image.hpp>
#include <tsin/display/DecoratedImage.hpp>
#include <tsin/config/Config.hpp>
#include <tsin/util/Resolution.hpp>
#include <tsin/util/Logger.hpp>
#include <tsin/util/EventListener.hpp>

#include <cstring>

using namespace tsin;

class MotionEvent : public PlayerEvent, EventListener
{
        float x, y;

        float left, right, bottom, top;
public:
        MotionEvent() {
                currentGame->input.registerListener(this, 0);
        }

        int handle(EventSource *source, int eventNo)
        {
                InputEvent *event = static_cast<InputEvent *>(source->getEventInfo());
                switch(event->type) {
                case INPUT_EVENT_PLAYER_MOVEMENT:
                        float speed = event->data.movement.slow ? 0.1f : 0.3f;
                        x = speed * event->data.movement.x;
                        y = speed * event->data.movement.y;
                        break;
                }
                return 0;
        }

        virtual void init(Player &player)
        {
                GameViewport *viewport = currentGame->game->getViewport(0);
                left = viewport->x() + player.w/4;
                bottom = viewport->y() + player.h/4;
                right = left + viewport->width() - player.w/2;
                top = bottom + viewport->height() - player.h/2;
        }

        virtual bool trigger(Player &player, bool early)
        {
                return early;
        }

        virtual PlayerEvent *handle(Player &player, bool early)
        {
                player.x += x;
                player.y += y;
                if (player.x < left)
                        player.x = left;
                if (player.x > right)
                        player.x = right;
                if (player.y < bottom)
                        player.y = bottom;
                if (player.y > top)
                        player.y = top;
                return this;
        }
};

class DresserEvent : public PlayerEvent, EventListener
{
        Display *display;
        Action action;
        bool slow;

        Character character;
        DecoratedImage *images[3];
        DecoratedImage *slowmode;
        float slowmodeW, slowmodeH;
        float slowmodeAngle;
        int slowmodeTick;
public:
        DresserEvent(Character character) : action(IDLE), slow(false), character(character) {
                currentGame->input.registerListener(this, 0);
                memset(images, 0, sizeof(images));
                display = &currentGame->display;
                slowmodeAngle = 0.0f;
                slowmodeTick = 0;
        }

        ~DresserEvent() {
                for (int i = 0; i < 3; i++)
                        delete images[i];
                delete slowmode;
        }

        int handle(EventSource *source, int eventNo)
        {
                InputEvent *event = static_cast<InputEvent *>(source->getEventInfo());
                switch(event->type) {
                case INPUT_EVENT_PLAYER_MOVEMENT:
                        slow = (event->data.movement.slow > 0);
                        if (event->data.movement.x < 0)
                                action = MOVE_LEFT;
                        else if (event->data.movement.x > 0)
                                action = MOVE_RIGHT;
                        else
                                action = IDLE;
                        break;
                }
                return 0;
        }

        virtual void init(Player &player)
        {
                StandardResolution res = currentGame->config.resolution;
                Action d[] = {IDLE, MOVE_LEFT, MOVE_RIGHT};
                ImageInfo info;

                for (int i = 0; i < 3; i++) {
                        BuiltinImages::inquiry(TOUHOU_PLAYER, character, d[i], info);
                        Image *origImage = currentGame->display.openImage(info.path, res);
                        DecoratedImage *image = new DecoratedImage(*origImage);
                        image->init(&info);
                        if (i == 0) {
                                player.w = Resolution::pixel2rangeX(res, info.w);
                                player.h = Resolution::pixel2rangeY(res, info.h);
                        }
                        origImage->unref();

                        images[i] = image;
                }

                BuiltinImages::inquiry(TOUHOU_EFFECT, SLOW_MODE_RING, info);
                Image *origImage = currentGame->display.openImage(info.path, res);
                DecoratedImage *image = new DecoratedImage(*origImage);
                image->init(&info);
                slowmodeW = Resolution::pixel2rangeX(res, info.w);
                slowmodeH = Resolution::pixel2rangeY(res, info.h);
                origImage->unref();

                slowmode = image;

                player.image = images[action];
        }

        virtual bool trigger(Player &player, bool early)
        {
                return true;
        }

        virtual PlayerEvent *handle(Player &player, bool early)
        {
                if (early) {
                        if (player.image != images[action] || player.image->reversed()) {
                                if (action == IDLE && player.image->curFrame() > 0) {
                                        player.image->setReverse(true);
                                        goto end;
                                }
                                if (player.image != images[action]) {
                                        player.image = images[action];
                                        player.image->setFrame(0);
                                }
                                player.image->setReverse(false);
                        }
end:
                        action = IDLE;
                        display->blitImage(*player.image, &player);
                } else {
                        if (slow) {
                                slowmodeTick ++;
                                slowmodeAngle += 4e-2;

                                float ratio = 1.0f;
                                if (slowmodeTick < 10)
                                        ratio = 0.05f + 0.15f * slowmodeTick;
                                else if (slowmodeTick < 18)
                                        ratio = 1.4f - 0.05 * (slowmodeTick - 10);

                                float alpha = 1.0f;
                                if (slowmodeTick < 18)
                                        alpha = 0.05f * slowmodeTick;
                                slowmode->setRGBA(1.0f, 1.0f, 1.0f, alpha);

                                float w = slowmodeW * ratio;
                                float h = slowmodeH * ratio;
                                display->blitImage(*slowmode,
                                                   player.x, player.y,
                                                   w, h, slowmodeAngle, zNear,
                                                   Display::HORIZONTAL_CENTER | Display::VERTICAL_CENTER);
                                display->blitImage(*slowmode,
                                                   player.x, player.y,
                                                   w, h, -slowmodeAngle, zNear,
                                                   Display::HORIZONTAL_CENTER | Display::VERTICAL_CENTER);
                        } else
                                slowmodeTick = 0;
                }
                return this;
        }
};

Player *TouhouPlayerFactory::generatePlayer(Character character)
{
        Player *player = new Player;
        GameViewport *viewport = currentGame->game->getViewport(0);

        player->x = viewport->x() + viewport->width() / 2;
        player->y = viewport->y() + viewport->height() / 4;
        player->angle = 0.0f;
        player->z = zNear;
        player->align = Display::HORIZONTAL_CENTER | Display::VERTICAL_CENTER;
        player->character = character;

        DresserEvent *dresser = new DresserEvent(character);
        dresser->init(*player);
        player->addEvent(dresser);

        MotionEvent *motion = new MotionEvent;
        motion->init(*player);
        player->addEvent(motion);

        return player;
}
